package com.prodog.database.wrapper;

import cn.hutool.core.io.FileUtil;
import com.prodog.utils.string.JSONUtils;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;

import java.io.File;
;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Predicate;
import java.util.stream.Collectors;

@Data
public class LocalDataWrapper<T, P> extends AbstractDataWrapper<T, P> {
    @Value("${game.rootPath}")
    private String rootPath;
    private String dataPath;
    private Map<P, T> dataMap;
    private Map<P, String> pathMap;

    private ReentrantLock lock = new ReentrantLock();

    public boolean insert(T obj) {
        try {
            P objId = (P) getFieldVal(obj, "id");
            insert(obj, String.valueOf(objId));
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean insert(T obj, String fileName) {
        try {
            P objId = (P) getFieldVal(obj, "id");

            if (dataMap.containsKey(objId)) {
                return false;
            } else {
                lock.lock();
                try {
                    dataMap.put(objId, obj);
                    //写入到文件
                    File file = new File(rootPath + "/" + dataPath, fileName + ".json");
                    String jsonStr = JSONUtils.toJSONString(obj, true);
                    FileUtil.writeUtf8String(jsonStr, file);
                    pathMap.put(objId, file.getAbsolutePath());
                } finally {
                    lock.unlock();
                }
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean update(T obj) {
        try {
            P objId = (P) getFieldVal(obj, "id");

            if (!dataMap.containsKey(objId)) {
                return false;
            } else {
                lock.lock();
                try {
                    //更新到数据map
                    dataMap.put(objId, obj);
                    //更新文件内容
                    File file = new File(pathMap.get(objId));
                    String jsonStr = JSONUtils.toJSONString(obj, true);
                    FileUtil.writeUtf8String(jsonStr, file);
                    pathMap.put(objId, file.getAbsolutePath());
                } finally {
                    lock.unlock();
                }
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean save(T obj) {
        try {
            P objId = (P) getFieldVal(obj, "id");

            if (!dataMap.containsKey(objId)) {
                return this.insert(obj);
            } else {
                return update(obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public T getById(P id) {
        return id == null ? null : dataMap.get(id);
    }

    public List<T> getByIds(Collection<P> ids) {
        List<T> datas = new ArrayList<>();
        for (P id : ids) {
            T data = dataMap.get(id);
            if (data != null) {
                datas.add(data);
            }
        }
        return datas;
    }

    public boolean removeById(P id) {
        if (getById(id) == null) {
            return false;
        }
        File file = new File(pathMap.get(id));
        file.delete();
        dataMap.remove(id);
        pathMap.remove(id);
        return true;
    }

    public boolean removeByIds(Collection<P> ids) {
        for (P id : ids) {
            removeById(id);
        }
        return true;
    }

    public List<T> list() {
        return new ArrayList<>(dataMap.values());
    }

    public List<T> list(Predicate<T> predicate) {
        return list().stream().filter(predicate).collect(Collectors.toList());
    }

    private Object getFieldVal(T data, String name) {
        try {
            Field field = getTypeClass().getDeclaredField(name);
            field.setAccessible(true);
            return field.get(data);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public T getByColumn(String column, Object val) {
        for (T data : list()) {
            if (getFieldVal(data, column).equals(val)) {
                return data;
            }
        }
        return null;
    }

    public List<T> listByColumn(String column, Object val) {
        List<T> res = new ArrayList<>();
        for (T data : list()) {
            if (getFieldVal(data, column).equals(val)) {
                res.add(data);
            }
        }
        return res;
    }

    public long countByColumn(String column, Object val) {
        long count = 0;
        for (T data : list()) {
            if (getFieldVal(data, column).equals(val)) {
                count++;
            }
        }
        return count;
    }

    public long max(String column) {
        long max = 0;
        for (T data : list()) {
            long curr = (long) getFieldVal(data, column);
            if (curr > max) {
                max = curr;
            }
        }
        return max;
    }

    public T getByColumns(Object... items) {
        for (T data : list()) {
            boolean eq = true;
            for (int i = 0; i < items.length; i += 2) {
                if (!getFieldVal(data, (String) items[i]).equals(items[i + 1])) {
                    eq = false;
                    break;
                }
            }
            if (eq) {
                return data;
            }
        }
        return null;
    }

    public List<T> listByColumns(Object... items) {
        List<T> res = new ArrayList<>();
        for (T data : list()) {
            boolean eq = true;
            for (int i = 0; i < items.length; i += 2) {
                if (!getFieldVal(data, (String) items[i]).equals(items[i + 1])) {
                    eq = false;
                    break;
                }
            }
            if (eq) {
                res.add(data);
            }
        }
        return res;
    }
}
